import requests
import os
import subprocess
import logging
import datetime 

#本脚本按以下目录处理任务
#   git-sync
#       - github-sync.py
#   org文件夹
#   org文件夹
#       - org下的仓库1
#       - org下的仓库2
#       - org下的仓库3


# git 使用建议
# git config --global credential.helper store
# git config --global user.name "your_username"
# git config --global user.email "email"

# 组织列表，自行配置
orgs_gem5={
    "gem5"
    }
orgs_riscv={
    "riscv",
    "riscv-non-isa",
    "riscv-software-src",
    "riscv-collab",
    "riscv-android-src",
    "riscv-admin",
    "riscvarchive"
    }
orgs_openhw={
    "openhwgroup"
    }
orgs_llvm={
    "llvm"
    }

orgs_xs={
    "OpenXiangShan"
    }
    
def check_url(url):
    #    检查一个网址是否可以访问。
    #    参数:
    #        url (str): 要检查的网址。
    #    返回:
    #        bool: 如果网址可以访问，返回 True；否则返回 False。
    # 测试函数
    # if __name__ == "__main__":
    #     url = input("请输入要检查的网址: ")
    #     if check_url(url):
    #         logging.info("网址可以访问！")
    #     else:
    #         logging.info("网址无法访问！")
    try:
        # 发送 GET 请求
        response = requests.get(url, timeout=10)  # 设置超时时间，避免长时间等待
        # 检查状态码是否为 200（成功）
        if response.status_code == 200:
            #logging.info("网址可以访问！")
            return True
        else:
            #logging.info("网址无法访问！")
            return False
    except requests.exceptions.RequestException as e:
        # 捕获请求异常，如网络错误、超时等
        #logging.info(f"发生错误: {e}")
        return False

def setmirror():
    """
    根据检查结果返回镜像源。

    返回:
        str: 如果网址可以访问，返回对应的域名；否则返回错误信息。
    """
    # 检查第一个网址
    if check_url("https://github.com/riscv"):
        return "github.com"
    # 如果第一个网址无法访问，检查第二个网址
    elif check_url("https://kkgithub.com/riscv"):
        logging.info("github无法访问，使用镜像网站kkgithub替换")
        return "kkgithub.com"
    else:
        # 如果两个网址都无法访问，抛出异常并报错
        raise RuntimeError("github及其镜像网站都无法访问！")

def check_and_change_to_mirrorsite(url):
    """
    将给定的GitHub URL替换为镜像网站的URL。

    参数:
        url (str): 要替换的GitHub URL。

    返回:
        str: 替换后的URL，如果URL不是GitHub的URL，则返回原URL。
    """
    # 检查URL是否以"URL_ADDRESS    # 检查URL是否以"https://github.com/"开头
    if url.startswith("https://github.com/"):
        #检查github能否访问，可以的话，就不替换
        if check_url("https://github.com/"):
            return url
        else:
            #将输入url中的https://github.com/替换为https://kkgithub.com/
            return url.replace("https://github.com/", "https://kkgithub.com/")
    else:
        # 如果URL不是GitHub的URL，返回原URL
        return url

def is_shallow_clone(local_repo_path):
    """
    使用 git rev-parse --is-shallow-repository 检查本地仓库是否是浅克隆仓库。

    参数:
        local_repo_path (str): 本地仓库的路径。

    返回:
        bool: 如果是浅克隆仓库，返回 True；否则返回 False。
    """
    try:
        # 运行 git rev-parse --is-shallow-repository 命令
        result = subprocess.run(
            ["git", "-C", local_repo_path, "rev-parse", "--is-shallow-repository"],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            check=True,
            text=True
        )
        # 检查输出是否为 'true'
        if result.stdout.strip() == 'true':
            logging.info(f"本地仓库 {local_repo_path} 是浅克隆仓库。")
        else:
            logging.info(f"本地仓库 {local_repo_path} 不是浅克隆仓库。")
        return result.stdout.strip() == 'true'
    except subprocess.CalledProcessError as e:
        logging.info(f"检查仓库是否为浅克隆时出错: {e}")
        return False

def get_github_repos_api_all(username):
    """
    获取指定GitHub账户下的所有仓库地址。

    参数:
        username (str): GitHub账户的用户名。

    返回:
        list: 包含仓库URL的列表，如果账户不存在或无权限访问，则返回空列表。

    example:
        if __name__ == "__main__":
            username = input("请输入GitHub用户名: ")
            repos = get_github_repos_api_all(username)
            if repos:
                logging.info(f"\n以下是 {username} 的所有仓库地址：")
                for repo in repos:
                    logging.info(repo)
            else:
                logging.info(f"未找到 {username} 的仓库，请检查用户名或网络连接。")
    """
    # 初始化参数
    repos = []
    page = 1
    per_page = 100  # 每页获取最多 100 个结果（GitHub API 的最大值）

    while True:
        # GitHub API endpoint for listing repositories of a user
        url = f"https://api.github.com/users/{username}/repos?page={page}&per_page={per_page}"
        try:
            response = requests.get(url)
            response.raise_for_status()  # 检查请求是否成功（状态码 200-299）

            # 提取仓库数据
            repos_page = response.json()
            if not repos_page:  # 如果该页没有仓库，结束循环
                break

            # 提取仓库的URL
            repos.extend([repo["html_url"] for repo in repos_page])

            # 增加页码，获取下一页
            page += 1

        except requests.exceptions.HTTPError as http_err:
            if response.status_code == 404:
                logging.info(f"错误：用户 {username} 不存在或无权限访问。")
                return []
            else:
                logging.info(f"HTTP 错误: {http_err}")
                return []
        except requests.exceptions.RequestException as err:
            logging.info(f"请求错误: {err}")
            return []
        except ValueError as json_err:
            logging.info(f"JSON 解析错误: {json_err}")
            return []

    return repos

def save_set_to_file(folder_name, file_name, data_set, mode="merge"):
    """
    在脚本的上一级文件夹中创建一个文件夹，并在该文件夹中创建一个文件，将集合保存到文件中。

    参数:
        folder_name (str): 要创建的文件夹名称。
        file_name (str): 要创建的文件名称（包括扩展名）。
        data_set (set): 要保存的集合数据,按行保存并按字母排序。
        mode (str): 写入模式，默认为 'merge'，支持 'merge' 和 'overwrite'。

    返回:
        str: 文件路径，如果发生错误返回 None。

    example:
        if __name__ == "__main__":
            # 定义集合
            my_set = {1, 2, 5,"a"}

            # 调用函数
            save_set_to_file("my_folder", "my_file.txt", my_set)
    """
    try:
        # 获取脚本的上一级目录
        script_dir = os.path.dirname(os.path.abspath(__file__))
        parent_dir = os.path.dirname(script_dir)

        # 创建文件夹路径
        folder_path = os.path.join(parent_dir, folder_name)

        # 确保文件夹存在
        if not os.path.exists(folder_path):
            os.makedirs(folder_path)

        # 创建文件路径
        file_path = os.path.join(folder_path, file_name)

        existing_data = set()  # 用于存储现有数据

        # 如果文件存在且模式为 'merge'，则读取现有内容
        if mode == "merge" and os.path.exists(file_path):
            with open(file_path, 'r') as file:
                for line in file:
                    stripped_line = line.strip()
                    if stripped_line:  # 跳过空行
                        existing_data.add(stripped_line)

        # 根据模式决定写入的数据
        if mode == "overwrite":
            merged_data = data_set
        elif mode == "merge":
            # 合并新集合和现有集合，并去重
            merged_data = set(str(element) for element in data_set).union(existing_data)
        else:
            raise ValueError("Invalid mode. Supported modes are 'merge' and 'overwrite'.")

        # 将合并后的数据转换为有序列表
        sorted_data = sorted(merged_data)

        # 打开文件并写入排序后的内容
        with open(file_path, 'w') as file:
            for element in sorted_data:
                file.write(f"{element}\n")

        logging.info(f"集合已成功保存到文件: {file_path}")
        return file_path

    except Exception as e:
        logging.info(f"发生错误: {e}")
        return None

def read_txt_to_set(file_path):
    """
    在一个txt文件中内容读出并返回为一个集合，每一行是一个元素
    参数:
        file_path (str): 要读取的文件路径。
    example:
        if __name__ == "__main__":
            file_path = "../gem5/repos_list.txt"
            result_set = read_txt_to_set(file_path)
            if result_set:
                logging.info("读取的集合:")
                logging.info(result_set)
    """
    try:
        with open(file_path, 'r', encoding='utf-8') as file:
            # 读取文件的每一行，去除行末的换行符
            lines = [line.strip() for line in file.readlines()]
            # 过滤掉空行和仅包含空格的行
            non_empty_lines = [line for line in lines if line]
            # 将列表转换为集合，自动去重
            result_set = set(non_empty_lines)
            # 获取到本地仓库列表
            logging.info(f"成功读取本地仓库列表: {result_set}")
            # 返回集合
            return result_set
    except FileNotFoundError:
        logging.info(f"文件 {file_path} 未找到，请检查文件路径。")
    except Exception as e:
        logging.info(f"读取文件时出现错误: {e}")

def create_github_org_dir(username_set):
    """
    根据用户名集合创建github组织文件夹，并且保存仓库列表文件

    example:
        if __name__ == "__main__":
            create_github_org_dir(users_gem5)
    """
    for user_org in username_set:
        repos = get_github_repos_api_all(user_org)
        save_set_to_file(user_org, "repos_list.txt", repos)
    return None

def is_git_repo(url):
    """
    检查 URL 是否是一个 Git 仓库 URL.

    参数:
        url (str): 要检查的 URL。

    返回:
        bool: 如果 URL 是一个 Git 仓库 URL，则返回 True；否则返回 False。

    example:
        if __name__ == "__main__":
            if is_git_repo("https://kkgithub.com/riscv/riscv-isa-manual"):
                logging.info("这是一个 Git 仓库 URL。")
            else:
                logging.info("这不是一个 Git 仓库 URL。")
    """
    try:
        # 执行 git ls-remote 命令来检查 URL 是否为有效的 Git 仓库
        result = subprocess.run(['git', 'ls-remote', url], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        # 如果命令执行成功（返回码为 0），则认为是有效的 Git 仓库
        return result.returncode == 0
    except Exception:
        return False

def git_repo_clone_full(url, local_dir):
    """
    full 克隆本地仓库。
    """
    repo_name = url.split('/')[-1].replace('.git', '')
    local_repo_path = os.path.join(local_dir, repo_name)
    try:
        # 使用 subprocess 调用 git clone 命令克隆远程仓库
        subprocess.run(['git', 'clone', url, local_repo_path], check=True)
        logging.info(f"仓库 {repo_name} 已成功克隆到 {local_repo_path}。")
        return True
    except subprocess.CalledProcessError as e:
        logging.info(f"克隆仓库 {repo_name} 时出错: {e}")
        return False

def get_remote_commit_count(url):
    """
    获取远程仓库的提交数量
    无法精确获得远程仓库所有提交数量
    参数:
        url (str): 远程仓库的 URL。
    注意：
        1. 这个函数假设每个提交对应一行输出，所以简单地统计输出的行数。
        2. 这个函数假设远程仓库存在，否则会抛出异常。
    """
    try:
        cmd = ['git', 'ls-remote', '--heads', url]
        result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True)
        output = result.stdout.decode('utf-8')
        # 简单假设每个提交对应一行输出
        return len(output.strip().splitlines())
    except subprocess.CalledProcessError as e:
        logging.error(f"获取远程仓库提交数量时出错: {e}")
        return 0

def get_local_commit_count(repo_path):
    """
    获取本地仓库的提交数量
    """
    try:
        cmd = ['git', '-C', repo_path, 'rev-list', '--count', 'HEAD']
        result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True)
        return int(result.stdout.decode('utf-8').strip())
    except subprocess.CalledProcessError as e:
        logging.error(f"获取本地仓库提交数量时出错: {e}")
        return 0

def git_repo_clone_depth(url, local_dir, max_depth=5):
    """
    使用递增的 depth 参数逐步克隆 Git 仓库，并在所有尝试完成后取消浅克隆。

    参数:
        url (str): 要克隆的 Git 仓库的 URL。
        local_dir (str): 本地目录，用于存储克隆的仓库。
        max_depth (int): 最大的 depth 参数值，默认为 5。

    返回:
        bool: 如果克隆成功，则返回 True；否则返回 False。
    """
    # 解析仓库名称和本地存储路径
    repo_name = url.split('/')[-1].replace('.git', '')
    local_repo_path = os.path.join(local_dir, repo_name)

    # 检查目标路径是否已存在
    if os.path.exists(local_repo_path):
        error_msg = f"目标路径 {local_repo_path} 已存在，无法克隆仓库。"
        logging.error(error_msg)
        raise ValueError(error_msg)

    error_count = 0  # 记录网络或远程仓库错误次数
    success = False  # 标记克隆是否成功

    # 获取远程仓库的提交数量
    remote_commit_count = get_remote_commit_count(url)
    if remote_commit_count == 0:
        return False
    
    


    # 逐级递增 depth，直到成功克隆或达到最大深度
    for depth in range(1, max_depth + 1):
        try:
            # 先使用depth=1进行浅克隆
            if depth == 1:
                # 使用 subprocess 调用 git clone 命令克隆远程仓库
                git_clone_cmd = ["git", "clone", "--depth", str(depth), url, local_repo_path]
                logging.info(f"尝试使用 depth={depth} 克隆仓库...,command: {git_clone_cmd}")
                subprocess.run(git_clone_cmd, check=True)
                logging.info(f"仓库已成功克隆，使用 depth={depth}。")
                success = True
            # 再使用git fetch进行进一步拉取
            else:
                # 使用 subprocess 调用 git fetch 命令克隆远程仓库
                # "git", "-C", local_repo_path, "pull", "--unshallow"]
                git_fetch_cmd = ["git", "-C", local_repo_path, "fetch", "--depth", str(depth)]
                logging.info(f"尝试使用 depth={depth} 进一步拉取仓库...,command: {git_fetch_cmd}")
                subprocess.run(git_fetch_cmd, check=True)
                logging.info(f"仓库已成功拉取，使用 depth={depth}。")
                success = True

            # 检查本地仓库的提交数量
        #    local_commit_count = get_local_commit_count(local_repo_path)
        #    if local_commit_count >= remote_commit_count:
        #        success = True
        #        logging.info("所有历史记录已克隆完成！,local_commit_count: {local_commit_count}, remote_commit_count: {remote_commit_count}")
        #        break  # 如果克隆完整，退出循环

        except subprocess.CalledProcessError as e:
            # 捕获克隆失败的错误
            error_str = str(e)
            if "Could not resolve host" in error_str or "Remote: fatal" in error_str:
                # 如果是网络或远程仓库问题，增加错误计数
                error_count += 1
                logging.warning(f"克隆尝试失败（depth={depth}），错误计数: {error_count}")
                if error_count >= 5:  # 设置最大错误次数为 5
                    logging.error("达到最大错误次数，克隆失败！")
                    return False
            else:
                # 其他类型的克隆错误
                logging.error(f"克隆仓库时出错（depth={depth}）: {e}")
                success = False

    if is_shallow_clone(local_repo_path):
        # 尝试取消浅克隆
        try:
            logging.info("尝试取消浅克隆...")
            subprocess.run(["git", "-C", local_repo_path, "pull", "--unshallow"], check=True)
            if not is_shallow_clone(local_repo_path):
                logging.info("已成功取消浅克隆，所有历史记录已克隆完成！,local_commit_count: {local_commit_count}, remote_commit_count: {remote_commit_count}")
                success = True
        except subprocess.CalledProcessError as e:
            logging.error(f"取消浅克隆时出错: {e}")
            success = False

    return success

def clone_repository(url, local_dir):
    """
    克隆 Git 仓库，首先尝试全面克隆，如果失败则尝试逐级递增的 depth 克隆。

    参数:
        url (str): 要克隆的 Git 仓库 URL。
        local_dir (str): 克隆的 Git 仓库保存的本地目录。

    返回:
        str: 如果克隆成功，返回克隆的仓库路径；否则返回 None。
    """
    # 尝试全面克隆
    #if git_repo_clone_full(url, local_dir):
    #    logging.info(f"全面克隆成功，仓库已保存到 {local_dir}")
    #    return local_dir

    # 如果全面克隆失败，尝试逐级递增的 depth 克隆
    if git_repo_clone_depth(url, local_dir):
        logging.info(f"成功逐级递增克隆仓库到 {local_dir}")
        return local_dir
    else:
        # 所有尝试都失败
        logging.info("所有克隆尝试失败，无法克隆仓库。")
        return None



def check_and_add_remote_repo(repo_path, remote_url):
    """
    添加远程仓库到本地仓库。

    :param repo_path: 本地 Git 仓库的路径
    :param remote_url: 远程仓库的 URL
    :return: 如果添加成功，返回 True；否则返回 False
    """
    try:
        # 检查仓库路径是否存在
        if not os.path.exists(repo_path):
            logging.info(f"指定的仓库路径 {repo_path} 不存在。")
            return False
        # 检查是否已经添加了远程仓库
        if is_remote_repo_added(repo_path):
            #logging.info(f"仓库 {repo_path} 已经添加了远程仓库。")
            return True
        else:
            # 添加远程仓库
            add_remote_cmd = ['git', '-C', repo_path, 'remote', 'add', 'origin', remote_url]
            logging.info(f"仓库中没有remote，尝试添加远程仓库 {remote_url} 到 {repo_path}")
            subprocess.run(add_remote_cmd, check=True)
            logging.info(f"已成功添加远程仓库 {remote_url} 到 {repo_path}")
            return True
    except subprocess.CalledProcessError as e:
        logging.error(f"check_and_add_remote_repo：添加远程仓库时出错: {e}")
        return False
def is_remote_repo_added(repo_path):
    """
    检查本地 Git 仓库是否已经添加了远程仓库。

    :param repo_path: 本地 Git 仓库的路径
    :return: 如果已经添加了远程仓库，返回 True；否则返回 False
    """
    try:
        # 检查仓库路径是否存在
        if not os.path.exists(repo_path):
            logging.info(f"指定的仓库路径 {repo_path} 不存在。")
            return False
        # 检查远程仓库列表
        remote_cmd = ['git', '-C', repo_path, 'remote', '-v']
        result = subprocess.run(remote_cmd, capture_output=True, text=True)
        if 'origin' in result.stdout:
            return True
        # 如果列表为空，则返回 False
        elif result.stdout.strip() == '':
            logging.info(f"is_remote_repo_added：仓库 {repo_path} 没有远程仓库。")
            return False
        
    except subprocess.CalledProcessError as e:
        logging.error(f"检查远程仓库时出错: {e}")
        return False
    except Exception as e:
        logging.error(f"检查远程仓库时出错: {e}")
        return False
    

def update_local_repo(repo_path,remote_url):
    """
    更新本地 Git 仓库。

    :param repo_path: 本地 Git 仓库的路径
    :return: 如果更新成功，返回 True；否则返回 False
    """
    try:
            # 检查仓库路径是否存在
        if not os.path.exists(repo_path):
            logging.info(f"指定的仓库路径 {repo_path} 不存在。")
            return False
        # 设置本地git仓库不检查文件权限
        subprocess.run(['git', '-C', repo_path, 'config', 'core.fileMode', 'false'], check=True)
        # 检查远程仓库，如果没有，增加远程仓库,默认仓库名为origin
        check_and_add_remote_repo(repo_path, remote_url)
        # 检查本地仓库是否有未提交的更改
        check_dirty_cmd = ['git', '-C', repo_path, 'status', '--porcelain']
        result = subprocess.run(check_dirty_cmd, capture_output=True, text=True)
        if result.stdout.strip():
            # 强制拉取，忽略本地更改
            pull_force_cmd = ['git', '-C', repo_path, 'reset', '--hard']
            logging.info(f"本地仓库有未提交的更改，将强制拉取覆盖本地更改。命令：{pull_force_cmd}")
            pull_result=subprocess.run(pull_force_cmd, check=True, capture_output=True, text=True)
            # 打印pull_result.stdout
            logging.info(f"强制拉取结果: {pull_result.stdout} 。")
        else:
            # 尝试拉取更新
            pull_cmd = ['git', '-C', repo_path, 'pull']
            logging.info("尝试拉取更新...")
            pull_result=subprocess.run(pull_cmd, capture_output=True, text=True)
            # 打印pull_result.stdout
            logging.info(f"拉取结果: {pull_result.stdout} 。")

        # 上游分支情况处理
        # 如果提示“There is no tracking information for the current branch.”，
        # 则使用“git branch --set-upstream-to=<remote>/<branch> main”将与本地分支同名的远程分支设置为上游分支
        if "There is no tracking information for the current branch." in pull_result.stdout:
            logging.info("当前分支没有上游分支，将设置上游分支。")
            # 获取本地分支名
            branch_cmd = ['git', '-C', repo_path, 'rev-parse', '--abbrev-ref', 'HEAD']
            branch_result = subprocess.run(branch_cmd, capture_output=True, text=True)
            branch_name = branch_result.stdout.strip()
            # 组合上游分支名
            upstream_branch = f"origin/{branch_name}"
            # 设置上游分支
            logging.info(f"设置上游分支为 origin/{upstream_branch}。")
            set_upstream_cmd = ['git', '-C', repo_path, 'branch', '--set-upstream-to=', upstream_branch]
            subprocess.run(set_upstream_cmd, check=True)
        return True
    except subprocess.CalledProcessError as e:
        logging.error(f"update_local_repo：更新本地仓库时出现错误: {e}")
        return False
    except Exception as e:
        logging.error(f"update_local_repo：发生未知错误: {e}")
        return False


def handle_submodules(repo_path):
    """
    处理本地 Git 仓库中的子模块。

    :param repo_path: 本地 Git 仓库的路径
    """
    try:
        # 检查是否有子模块
        list_submodules_cmd = ['git', '-C', repo_path, 'config', '--file', '.gitmodules', '--get-regexp', 'path']
        result = subprocess.run(list_submodules_cmd, capture_output=True, text=True)
        submodule_paths = result.stdout.strip().split('\n')

        if not submodule_paths or submodule_paths == ['']:
            logging.info("此仓库中没有子模块。")
        else:
            logging.info("发现以下子模块：")
            for path_info in submodule_paths:
                path = path_info.split(' ')[1]
                logging.info(f" - {path}")

            # 初始化子模块
            submodule_init_cmd = ['git', '-C', repo_path, 'submodule', 'init']
            subprocess.run(submodule_init_cmd, check=True)
            logging.info("子模块已初始化。")

            # 更新子模块
            submodule_update_cmd = ['git', '-C', repo_path, 'submodule', 'update']
            subprocess.run(submodule_update_cmd, check=True)
            logging.info("子模块已更新到最新版本。")
    except subprocess.CalledProcessError as e:
        logging.info(f"处理子模块时出现错误: {e}")
    except Exception as e:
        logging.info(f"发生未知错误: {e}")


def update_local_repo_and_submodules(local_repo_path,remote_url):
    """
    更新本地 Git 仓库并处理其中的子模块。

    :param repo_path: 本地 Git 仓库的路径
    """
    if update_local_repo(local_repo_path,remote_url):
        handle_submodules(local_repo_path)

def process_url_list(url_list, local_dir):
    """
    遍历给定的 URL 列表，检查每个 URL 是否为有效的 Git 仓库。
    如果是有效的 Git 仓库，尝试克隆或更新本地仓库。
    example:
        file_path = "../gem5/repos_list.txt"
        url_list = read_txt_to_set(file_path)
        process_url_list(url_list, "../gem5")
    """
    repo_num=0
    for remote_url in url_list:
        try:
            repo_num=repo_num+1
            repo_name = remote_url.split('/')[-1].replace('.git', '')
            logging.info(f"\n\n({repo_num}/{len(url_list)})正在处理仓库{repo_name}, url={remote_url}")
            # 检查 URL 是否可访问
            response = requests.head(remote_url)
            if response.status_code == 200:
                if is_git_repo(remote_url):
                    
                    local_repo_path = os.path.join(local_dir, repo_name)

                    if os.path.exists(local_repo_path) and os.path.isdir(local_repo_path):
                        try:
                            ## 获取远程仓库的提交数量
                            #remote_commit_count = get_remote_commit_count(url)
                            ## 获取本地仓库的提交数量
                            #local_commit_count = get_local_commit_count(local_repo_path)
                            ## 使用 subprocess 调用 git pull 命令更新本地仓库
                            #if local_commit_count >= remote_commit_count:
                            #    success = True
                            #    logging.info(f"仓库 {repo_name} 已存在，所有历史记录已克隆完成！local_commit_count: {local_commit_count}, remote_commit_count: #{remote_commit_count}")
                            #else:
                            logging.info(f"仓库 {repo_name} 已存在，尝试更新, from:{remote_url}。")
                            if is_shallow_clone(local_repo_path):
                                subprocess.run(["git", "-C", local_repo_path, "pull", "--unshallow"], check=True)
                            else:
                                try:
                                    update_local_repo_and_submodules(local_repo_path,remote_url)
                                    logging.info(f"仓库 {repo_name} 已更新。")
                                except subprocess.CalledProcessError as e:
                                    logging.error(f"process_url_list：更新仓库 {repo_name} 时出错（1）: {e}")
                        except subprocess.CalledProcessError as e:
                            logging.info(f"process_url_list：更新仓库 {repo_name} 时出错（2）: {e}")
                    else:
                        logging.info(f"仓库 {repo_name} 不存在，尝试克隆, from:{remote_url}。")
                        clone_repository(remote_url, local_dir)
                else:
                    logging.info(f"{remote_url} 不是一个有效的 Git 仓库 URL。")
            else:
                logging.info(f"{remote_url} 无法访问，状态码: {response.status_code}。")
        except requests.RequestException as e:
            logging.info(f"process_url_list: 访问 {remote_url} 时出错: {e}")

def patch_git_sync(orgsname,mode="clone"):
    """
    遍历给定的组织名列表，获取每个组织的仓库列表，并将其保存到本地文件中。
    然后，遍历本地文件中的 URL 列表，检查每个 URL 是否为有效的 Git 仓库。
    如果是有效的 Git 仓库，尝试克隆或更新本地仓库。


    mode:
        "update_repolist" 更新本地仓库list。
        "clone" do not update repolist, only克隆本地仓库。
        "update_and_clone" 更新repo_list and clone仓库。
    example:
        for i in range(1):
            patch_git_sync(orgs_gem5,mode="clone")
    """
    logging.basicConfig(level=logging.INFO)
    for orgname in orgsname:
        logging.info(f"\n\n正在处理组织 {orgname}")
        # 1 update repos_list.txt
        if os.path.exists(f"../{orgname}/repos_list.txt"):
            logging.info(f"识别到本地repos文件 {orgname}/repos_list.txt")
            if (mode=="update_and_clone" or mode=="update_repolist"):
                # 从github获取组织的仓库列表
                repos = get_github_repos_api_all(orgname)
                # 保存到本地文件
                save_set_to_file(orgname, "repos_list.txt", repos)
            else:
                # jump this step
                logging.info(f"跳过更新本地repos文件 {orgname}/repos_list.txt")
                pass
        else:
            logging.info(f"本地repos文件 {orgname}/repos_list.txt 不存在，尝试从github获取组织的仓库列表。")
            # 从github获取组织的仓库列表
            repos = get_github_repos_api_all(orgname)
            # 保存到本地文件
            save_set_to_file(orgname, "repos_list.txt", repos)

        # 2 clone or update repos
        # 从本地文件获取仓库列表
        url_list = read_txt_to_set(f"../{orgname}/repos_list.txt")
        # 遍历仓库列表，克隆或更新本地仓库
        process_url_list(url_list, f"../{orgname}")

def patch_process_command(orgsname,command):
    """
    对orgsname下的所有仓库执行指令，指令为command

    param：
        orgsname: 组织名列表
        command: 要执行的命令,格式为
        return: 命令的输出
    example:
        patch_process_command(orgs_gem5,"git status")
    """
    logging.basicConfig(level=logging.INFO)
    for orgname in orgsname:
        # 从本地文件获取仓库列表
        if os.path.exists(f"../{orgname}/repos_list.txt"):
            logging.info(f"识别到本地repos文件 {orgname}/repos_list.txt")
            url_list = read_txt_to_set(f"../{orgname}/repos_list.txt")
        else:
            logging.info(f"本地文件../{orgname}/repos_list.txt 不存在，跳过。")
            break
        # 遍历仓库列表，执行command
        for url in url_list:
            repo_name = url.split('/')[-1].replace('.git', '')
            logging.info(f"正在处理仓库 {repo_name}")
            local_repo_path = f"../{orgname}/{repo_name}"
            if os.path.exists(local_repo_path) and os.path.isdir(local_repo_path):
                try:
                    # 使用 subprocess 对repo执行command指令
                    result = subprocess.run(command, shell=True, cwd=local_repo_path, capture_output=True, text=True)
                    if result.returncode == 0:
                        logging.info(f"命令 {command} 成功执行，输出: ")
                        print(f"{result.stdout}")
                    else:
                        logging.info(f"命令 {command} 执行失败，错误: ")
                        print("{result.stderr}")

                except subprocess.CalledProcessError as e:
                    logging.info(f"执行命令 {command} 时出错: {e}")
            else:
                logging.info(f"仓库 {repo_name} 不存在，跳过。")
                pass
            logging.info(f"仓库 {repo_name} 处理完成。")
        logging.info(f"组织 {orgname} 处理完成。")
        return 0


################### main
if __name__ == "__main__":
    # saving logging to a file, file name is log_DATE_TIME.txt. and still print to console
    # 配置日志记录器
    logger = logging.getLogger()
    logger.setLevel(logging.INFO)

    # 创建文件处理器
    file_handler = logging.FileHandler(f"log_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.txt")
    file_handler.setLevel(logging.INFO)

    # 创建流处理器
    stream_handler = logging.StreamHandler()
    stream_handler.setLevel(logging.INFO)

    # 创建格式化器并添加到处理器
    formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
    file_handler.setFormatter(formatter)
    stream_handler.setFormatter(formatter)

    # 将处理器添加到日志记录器
    logger.addHandler(file_handler)
    logger.addHandler(stream_handler)

    # 设置日志记录格式和级别
    logging.basicConfig(
        format='%(asctime)s [%(lineno)d] %(message)s',  # 添加时间和行号
        datefmt='%Y-%m-%d %H:%M:%S',  # 时间格式
        level=logging.INFO
    )
    logging.info(f"{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}")
    
    # loop 10 times
    for i in range(10):
        #patch_process_command(orgs_riscv,"git pull")        # 
        #patch_process_command(orgs_riscv,"git status")        
        #patch_git_sync(orgs_openhw,mode="clone")
        #patch_git_sync(orgs_xs,mode="clone")
        patch_git_sync(orgs_riscv,mode="update_and_clone")
        #patch_process_command(orgs_gem5,"git pull")        # 

